<template>
  <div class="minio">
    <div class="infoTop">
      <div class="text">
        已选中<b>{{ uploadFileList.length }}</b>个文件
        <template v-if="uploadFileList.length">
          ，共<b>{{ getListSize(uploadFileList) }}</b>
        </template>
      </div>
      <div class="btngroup">
        <el-upload multiple ref="upload" action="" :accept="fileStyle" :on-remove="handleRemove"
          :on-change="handleFileChange" :file-list="uploadFileList" :show-file-list="false" :auto-upload="false">
          <template #trigger>
            <el-button plain type="primary"><span class="addFile">选择文件</span></el-button>
          </template>

          <el-button type="success" @click="handleUpload" style="margin-left: 15px"
            :disabled="disabledBtn">点击上传</el-button>
        </el-upload>
      </div>
    </div>

    <el-upload   v-if="!uploadFileList.length" drag multiple ref="upload" action="" :accept='fileStyle'
      :on-remove="handleRemove" :on-change="handleFileChange" :file-list="uploadFileList" :show-file-list="false"
      :auto-upload="false">
      <el-icon  class="el-icon--upload">
        <upload-filled />
      </el-icon>
      <div class="el-upload__text">
        <p style="line-height: 1em; margin-bottom: 8px;">拖拽到区域上传或<em>点击上传</em></p>
        <span style=" display: block; line-height: 1em;">（请上传小于300M的文件）</span>
      </div>
    </el-upload>

    <div class="table" v-else>
      <div class="row head">
        <div class="col col1">名称</div>
        <div class="col col2">大小</div>
        <div class="col col3">进度</div>
        <div class="col col4">状态</div>
        <div class="col col5">操作</div>
      </div>

      <div class="row" v-for="(item, index) in uploadFileList" :key="index">
        <div class="col col1">
          <svg class="coloricon" aria-hidden="true">
            <use :xlink:href="'#' + getFileIcon(item.name)"></use>
          </svg>
          {{ giveEllipsisStr(item.name, 25) }}
        </div>
        <div class="col col2">{{ filterSize(item.size) }}</div>
        <div class="col col3">
          <el-progress :percentage="item.uploadProgress" class="file-progress-value" />
        </div>
        <div class="col col4">
          <el-tag v-if="item.status === '等待上传'" size="small" type="info">等待上传</el-tag>
          <el-tag v-else-if="item.status === '校验MD5'" size="small" type="warning">校验文件</el-tag>

          <el-tag v-else-if="item.status === '正在上传'" size="small">正在上传</el-tag>
          <el-tag v-else-if="item.status === '上传成功'" size="small" type="success">上传完成</el-tag>
          <el-tag v-else size="small" type="danger">上传错误</el-tag>
        </div>
        <div class="col col5">
          <span class="del" @click="delRowUpload(index)">
            <i class="iconfont icon-delete-fill"></i>
          </span>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import {
  filterSize,
  getFileIcon,
  giveEllipsisStr,
} from "@/utils/common";
import { GetQueryValue1 } from "@/utils/minioUpload";
import {
  delChunk,
  delMinFile,
  getUpldUrl,
  chunkMerge,
  getUpFileUrl,
  saveUpSfile
} from "@/api/minIOupload";
import { UploadFilled } from "@element-plus/icons-vue";

import { ElMessage } from "element-plus";
import axios from "axios";
import uploadStore from "@/store/modules/upload";
import { watch } from "vue";

const uploadArr = ref([])
const uploadUrls = ref([])
const props = defineProps({
  fileStyle: {
    type: String
  }

})
const disabledBtn = ref(true)
const emits = defineEmits(["fileHzm"])
const store = uploadStore();
let uploadFileList = ref([]);

let currentFileIndex = 0;
const maxSize = 300 * 1024 * 1024;
const chunkSize = 10 * 1024 * 1024;
// 上传并发数
const simultaneousUploads = 3;
let uploadIdInfo = null;
const FileStatus = {
  wait: "等待上传",
  getMd5: "校验MD5",
  uploading: "正在上传",
  success: "上传成功",
  error: "上传错误",
};

//点击上传文件
const handleUpload = () => {
  disabledBtn.value = true
  const files = uploadFileList.value;
  if (files.findIndex((fileItem) => fileItem.size < 1) > -1)
    return ElMessage("存在空文件，请删除");

  let bool = files.some((item) => {
    return item.status == "正在上传" || item.status == "校验MD5";
  });

  if (bool) return ElMessage.warning("队列中有正在上传的文件，请稍后~~");

  let successBool = files.every((item) => {
    return item.status == "上传成功";
  });

  if (successBool)
    return ElMessage.warning("上传任务已全部完成，请勿重复上传~~");


  /**
   * 文件分片
   */
  const createFileChunk = (file, size = chunkSize) => {
    if (file.size < 1) return ElMessage.warning("不可上传空文件");
    const fileChunkList = [];
    let count = 0;
    while (count < file.size) {
      fileChunkList.push({
        file: file.slice(count, count + size),
      });
      count += size;
    }
    return fileChunkList;
  };


  // 获取大文件文件的urls
  const getFileUploadUrls = (fileParam) => {
    return getUpldUrl(fileParam);
  };


  //获取小文件上传的url方法
const getSFileUploadUrls = (fileParams) => {
  return getUpFileUrl(fileParams);
};



  // 当前操作文件
  const currentFile = files[currentFileIndex];
  currentFile.status = FileStatus.getMd5;
  /**
   * 处理即将上传的分片列表，判断是否有已上传的分片，有则从列表中删除
   */
  const processUploadChunkList = (chunkList) => {
    const currentFile = uploadFileList.value[currentFileIndex];
    let chunkUploadedList = currentFile.chunkUploadedList;
    if (
      chunkUploadedList === undefined ||
      chunkUploadedList === null ||
      chunkUploadedList.length === 0
    ) {
      return chunkList;
    }
    //
    for (let i = chunkList.length - 1; i >= 0; i--) {
      const chunkItem = chunkList[i];
      for (let j = 0; j < chunkUploadedList.length; j++) {
        if (chunkItem.chunkNumber === chunkUploadedList[j]) {
          chunkList.splice(i, 1);
          break;
        }
      }
    }
    return chunkList;
  };
  /**
   * 上传
   */
  const uploadChunkBase = (chunkList) => {
    uploadArr.value = []
    let successCount = 0;
    let totalChunks = chunkList.length;
    return new Promise((resolve) => {
      const handler = () => {

        if (chunkList.length) {
          const chunkItem = chunkList.shift();
          if (chunkItem.stop) {
            return;
          }
          // 直接上传二进制，不需要构造 FormData，否则上传后文件损坏
          axios.put(chunkItem.uploadUrl, chunkItem.chunk.file, {
            // 上传进度处理
            onUploadProgress: checkChunkUploadProgress(chunkItem),
            headers: {
              "Content-Type": "application/octet-stream",
            },
          })
            .then((response) => {

              if (response.status === 200) {
                console.log("分片：" + chunkItem.chunkNumber + " 上传成功");
                successCount++;
                // 继续上传下一个分片
                handler();
              } else {
                console.log(
                  "上传失败：" + response.status + "，" + response.statusText
                );
              }
              uploadArr.value.push(response)
            })
            .catch((error) => {
              if (!uploadFileList.value.length) return;
              if (chunkItem.stop) {
                return;
              }
              // 更新状态
              ElMessage.error(
                "分片：" + chunkItem.chunkNumber + " 上传失败" + error
              );
              // throw '结束上传'
              // console.log(uploadFileList.value)
              // 重新添加到队列中
              chunkList.push(chunkItem);
              handler();
            });
        }
        if (successCount >= totalChunks) {
          resolve();
        }
      };
      // 并发
      for (let i = 0; i < simultaneousUploads; i++) {
        handler();
      }
    });
  };


  /**
 * 检查分片上传进度
 */
  const checkChunkUploadProgress = (item) => {
    return (p) => {
      item.progress = parseInt(String((p.loaded / p.total) * 100));
      if (item.stop) return;
      updateChunkUploadStatus(item);
    };
  };

  const updateChunkUploadStatus = (item) => {
    if (!uploadFileList.value.length) return;
    let status = FileStatus.uploading;
    let progressStatus = "normal";
    if (item.progress >= 100) {
      status = FileStatus.success;
      progressStatus = "success";
    }
    let chunkIndex = item.chunkNumber - 1;
    let currentChunk =
      uploadFileList.value[currentFileIndex].chunkList[chunkIndex];
    // 修改状态
    currentChunk.status = status;
    currentChunk.progressStatus = progressStatus;
    // 更新状态
    // this.$set(this.uploadFileList.value[currentFileIndex].chunkList, chunkIndex, currentChunk)
    // 获取文件上传进度
    getCurrentFileProgress();
  };

  const getCurrentFileProgress = () => {
    const currentFile = uploadFileList.value[currentFileIndex];
    if (!currentFile || !currentFile.chunkList) {
      return;
    }
    const chunkList = currentFile.chunkList;
    const uploadedSize = chunkList
      .map((item) => item.chunk.file.size * item.progress)
      .reduce((acc, cur) => acc + cur);
    // 计算方式：已上传大小 / 文件总大小
    let progress = parseInt((uploadedSize / currentFile.size).toFixed(2));
    currentFile.uploadProgress = progress;
    // this.$set(this.uploadFileList, currentFileIndex, currentFile)
  };


  // 上传文件步骤  判断文件是否大于5M，大于5M走分片上传，小于走小文件上传
  if (currentFile.size / 1024 / 1024 > 5) {
    // 1.创建分片(10M一个分片,返回blob格式 数组)
    console.log(currentFile);
    
    let fileChunks = createFileChunk(currentFile.raw, chunkSize);
    if (!fileChunks) return;
    //  分片上传
    let params = {
      content_length: currentFile.size,
      file_name: currentFile.name,
      mime: null
    }
    // 2、获取上传文件的urls
    getFileUploadUrls(params).then(res => {
      // 当前操作文件的地址
      // 分片数组
      uploadIdInfo = res;
      uploadUrls.value = uploadIdInfo[0];
      if (fileChunks.length !== uploadUrls.value.length) {
        ElMessage.error("文件分片上传地址获取错误");
        return;
      }
      
      // 分片上传地址没有问题进行下一步
      // 将blob格式的分片添加到当前操作文件中
      fileChunks.map((chunkItem, index) => {
        currentFile.chunkList.push({
          chunkNumber: index + 1,
          chunk: chunkItem,
          uploadUrl: uploadUrls.value[index].url,
          progress: 0,
          status: "—",
        });
      });
      let tempFileChunks = [];
      currentFile.chunkList.forEach((item) => {
        tempFileChunks.push(item);
      });
      currentFile.status = FileStatus.uploading;
      // 处理分片列表，删除已上传的分片
      tempFileChunks = processUploadChunkList(tempFileChunks);
      // 3. 上传
      uploadChunkBase(tempFileChunks);
      watch(uploadArr.value, nv => {
        if (nv.length == uploadUrls.value.length) {
          console.log("分片已全部上传");
          // 4. 合并分片
          let options = uploadIdInfo[1];
          mergeFile(options).then(res => {
            if (res.status == "上传成功") {
              console.log(options);
              ElMessage.success("上传成功");
              currentFile.status = FileStatus.success;
              currentFile.uploadProgress = 100;
              autoNextUpload(uploadFileList.value);
              currentFile.file_id=res.file_id  
              console.log(uploadFileList.value);
              store.uploadFileList=uploadFileList.value
            } else {
              // 未上传
              console.log("未上传");
            }

          })
        }
      })
    });

  } else {
    // 小文件上传的逻辑
    return new Promise(async (resolve, reject) => {
      // 第一步:获取小文件上传的url
      let params = {
        "original_filename": currentFile.name,
        "content_length": currentFile.size
      }   
      let uploadIdInfoResult = await getSFileUploadUrls(params)
      let blob = createFileChunk(currentFile.raw, chunkSize)[0].file;
      //第二步  上传
      let uploadUrl = uploadIdInfoResult[0]
      let uploadId = uploadIdInfoResult[1]

      currentFile.status = FileStatus.uploading;
      if (uploadIdInfoResult.length) {
        // 直接上传二进制，不需要构造 FormData，否则上传后文件损坏
        axios.put(uploadUrl, blob, {
           // 上传进度处理
          headers: {
            'Content-Type': 'application/octet-stream',
          },
        }).then(response => {
          // 第三步 将文件保存到数据库
          saveUpSfile(uploadId).then(result => { 
           
            currentFile.uploadProgress = 100;
            currentFile.status = FileStatus.success;
            ElMessage.success('上传成功');
            autoNextUpload(uploadFileList.value);     
            currentFile.file_id=result 
            store.uploadFileList=uploadFileList.value      
              
          })
        }).catch(error => {
          console.log("上传失败" + error);
        })

      }   

    })



  }
};



//选择文件方法
const handleFileChange = (file, fileList) => {
  let index = fileList.findIndex((item) => {
    return item.size > maxSize;
  });
  if (index >= 0) fileList.splice(index, 1);

  if (file.size > maxSize) {
    ElMessage.error("文件超过200M限制,请压缩后上传~");
    return;
  }

  disabledBtn.value = false
  uploadFileList.value = fileList;
  uploadFileList.value.forEach((item) => {
    if (item.uid == file.uid) {
      // 初始化自定义属性
      initFileProperties(item);
    }
  });
};
//新上传文件初始化
const initFileProperties = (file) => {
  file.chunkList = [];
  file.status = FileStatus.wait;
  file.progressStatus = "warning";
  file.uploadProgress = 0;
};

//清空上传列表
const handleRemove = (file, fileList) => {
  uploadFileList.value = fileList;
};



/**
 * 合并文件
 * @param params 请求参数
 */
const mergeFile = (params) => {
  return new Promise((resolve, reject) => {
    chunkMerge(params)
      .then((res) => {        
        console.log(res);

        if (!res) {
          params.status = FileStatus.error;
          params.file_id=res
          resolve(params);
        } else {
          params.status = FileStatus.success;
           params.file_id=res
          resolve(params);
        }
      })
      .catch((error) => {
        ElMessage.error("合并文件失败：" + error);
        params.status = FileStatus.error;
        reject();
      });
  });
};

/**
 * 上传下标偏移
 */
const autoNextUpload = (files) => {
  currentFileIndex++;
  if (currentFileIndex < files.length) {
    handleUpload();
  }
};

/**
 * 保存uploadid到缓存
 */
let arr1 = []
const saveFileUploadId = (data) => {
  arr1.push(data)
  console.log(arr1);
  console.log(store.uploadFileList)
  if (arr1.length == store.uploadFileList.length) {
    store.uploadFileList = store.uploadFileList.map((item, index) => {
      return { ...item, ...arr1[index] };
    });
    console.log(store.uploadFileList);
    arr1 = []

  }


}


//获取列表中文件大小的和
const getListSize = (list) => {
  if (list.length <= 0) {
    return "0k";
  }
  let totalSize = 0;
  list.forEach((item) => {
    totalSize += item.size;
  });
  return filterSize(totalSize);
};

/**
 * 删除一条上传
 */
const delRowUpload = (index) => {
  let indexFile = uploadFileList.value[index];

  if (indexFile.status == "等待上传") {
    uploadFileList.value.splice(index, 1);
  } else if (indexFile.status == "校验MD5") {
    ElMessage.warning("校验中,请稍后");
  } else if (indexFile.status == "正在上传") {
    let url = indexFile.chunkList[0].uploadUrl;
    let uploadId = GetQueryValue1("uploadId", url);

    indexFile.chunkList.map((item) => {
      return (item.stop = true);
    });
    uploadFileList.value.splice(index, 1);
    delChunkFun([uploadId]).then((res) => {
      if (uploadFileList.value.length) {
        handleUpload();
      }
    });
  } else if (indexFile.status == "上传成功") {



    uploadFileList.value.splice(index, 1);
    currentFileIndex--;
    delMinFileFun([indexFile.ossId]).then((res) => {
      console.log(res);
    });
  }
};

/**
 * 删除已上传的文件
 */
const delMinFileFun = (arr) => {
  return new Promise((resolve, reject) => {
    delMinFile({
      keys: arr,
    })
      .then((res) => {
        resolve(res);
      })
      .catch((err) => {
        reject(err);
      });
  });
};

/**
 * 删除分片
 */
const delChunkFun = (arr) => {
  return new Promise((resolve, reject) => {
    delChunk({
      keys: arr,
    })
      .then((res) => {
        resolve(res);
      })
      .catch((err) => {
        reject(err);
      });
  });
};

watch(
  () => uploadFileList.value,
  (nv) => {
    store.setUploadFileList(nv);
  },
  { deep: true, immediate: true }
);

//监听上次列表的数据变化
watch(
  () => store.uploadFileList,
  (nv) => {
    uploadFileList.value = nv;
    if (nv == 0) {
      currentFileIndex = 0;
    }
  }
);
</script>

<style>
  .minio .el-upload-dragger{
    height: 400px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
 
}
</style>
<style lang="scss" scoped>


.minio {
  width: 100%;

  .infoTop {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 15px 0;

    .text {
      font-size: 14px;
      color: #888;
    }
  }

  .btngroup {
    &>div {
      display: flex;
    }

    .addFile {
      padding: 0 30px;
    }
  }

  .table {
    height: 500px;
    margin-top: 10px;
    overflow-y: auto;

    .row {
      display: flex;
      justify-content: space-between;
      align-items: center;
      height: 50px;

      &.head {
        background: #f6f7fb;

        .col {
          font-size: 16px;
          font-weight: 600;
        }
      }

      .col {
        padding: 0 10px;
        font-size: 14px;
        width: 20%;
        position: relative;

        .del {
          padding: 3px;
          cursor: pointer;
        }

        &.col1 {
          white-space: nowrap;
          overflow: hidden;
          text-overflow: ellipsis;
          flex: 1;

          .coloricon {
            font-size: 1.5em;
          }
        }

        &.col2 {
          width: 80px;
        }

        &.col3 {
          flex: 1;
        }

        &.col4 {
          width: 80px;
        }

        &.col5 {
          width: 60px;
        }
      }
    }
  }
}
</style>
